const BaseDao = require('./BaseDao');
const { Op } = require('sequelize');
const Order = require('../model/Order');
const Trade = require('../model/Trade');
const db = require('../config/DBconnector');
const { OrderType, OrderStatus, TradeStatus } = require('../constants/DbEnum');

/**
 * Order DAO
 */
class OrderDao extends BaseDao {
  /**
   * constructor
   */
  constructor() {
    super(Order);
  }

  /**
   * fetch max buy price
   * @param {CoinType} baseCoinType
   * @param {CoinType} counterCoinType
   * @return {Decimal} price
   */
  fetchMaxBuyPrice(baseCoinType, counterCoinType) {
    return this.model.max('price', {
      where: {
        orderType: OrderType.limitedBuy,
        status: OrderStatus.PENDING,
        baseCoinType,
        counterCoinType,
        currentBaseCoinAmount: { [Op.gt]: 0.0 },
      },
    });
  }

  /**
   * fetch min sell price
   * @param {CoinType} baseCoinType
   * @param {CoinType} counterCoinType
   * @return {Decimal} price
   */
  fetchMinSellPrice(baseCoinType, counterCoinType) {
    return this.model.min('price', {
      where: {
        orderType: OrderType.limitedSell,
        status: OrderStatus.PENDING,
        baseCoinType,
        counterCoinType,
        currentBaseCoinAmount: { [Op.gt]: 0.0 },
      },
    });
  }

  /**
   * list buy orders with min price
   * @param {Decimal} minPrice
   * @param {CoinType} baseCoinType
   * @param {CoinType} counterCoinType
   * @return {Array<Order>} orders
   */
  listBuyOrdersWithMinPrice(minPrice, baseCoinType, counterCoinType) {
    return this.model.findAll({
      where: {
        orderType: OrderType.limitedBuy,
        status: OrderStatus.PENDING,
        baseCoinType,
        counterCoinType,
        currentBaseCoinAmount: { [Op.gt]: 0.0 },
        price: { [Op.gte]: minPrice }, // price >= minPrice
      },
      order: [['price', 'desc'], ['expiredTimestamp', 'asc']],
      raw: true,
    });
  }

  /**
   * list sell orders with max price
   * @param {Decimal} maxPrice
   * @param {CoinType} baseCoinType
   * @param {CoinType} counterCoinType
   * @return {Array<Order>} orders
   */
  listSellOrdersWithMaxPrice(maxPrice, baseCoinType, counterCoinType) {
    return this.model.findAll({
      where: {
        orderType: OrderType.limitedSell,
        status: OrderStatus.PENDING,
        baseCoinType,
        counterCoinType,
        currentBaseCoinAmount: { [Op.gt]: 0.0 },
        price: { [Op.lte]: maxPrice }, // price <= maxPrice
      },
      order: [['price', 'asc'], ['expiredTimestamp', 'asc']],
      raw: true,
    });
  }

  /**
   * list my orders
   * @param {String} username
   * @param {Integer} offset default 0
   * @param {Integer} limit default 100
   * @return {Array<Order>} orders
   */
  listMyOrders(username, offset = 0, limit = 100) {
    let where = {
      username: username,
      status: [OrderStatus.PENDING, OrderStatus.COMPLETED],
    };
    let include = [
      { model: Trade, as: 'buyTrades', required: false },
      { model: Trade, as: 'sellTrades', required: false },
    ];
    let order = [['createdAt', 'desc']];

    return this.model.findAll({ where, include, order, offset, limit });
  }

  /**
   * list my order history
   * @param {String} username
   * @param {Integer} offset default 0
   * @param {Integer} limit default 100
   * @return {Array<Order>} orders
   */
  history(username, offset = 0, limit = 100) {
    let where = {
      username: username,
      [Op.or]: { historicalTradeCount: { [Op.gt]: 0 }, status: OrderStatus.CANCELED },
    };
    let include = [
      {
        model: Trade,
        as: 'buyTrades',
        required: false,
        where: { status: [TradeStatus.COMPLETED, TradeStatus.EXPIRED] },
      },
      {
        model: Trade,
        as: 'sellTrades',
        required: false,
        where: { status: [TradeStatus.COMPLETED, TradeStatus.EXPIRED] },
      },
    ];
    let order = [['updatedAt', 'desc']];

    return this.model.findAll({ where, include, order, offset, limit });
  }

  /**
   * find one order with trades
   * @param {String} username
   * @param {Integer} id of order
   * @param {OrderStatus} status of order
   * @return {Order} order
   */
  findOneWithTrades(username, id, status) {
    let where = {
      id,
      username,
    };
    let include = [
      { model: Trade, as: 'buyTrades', required: false },
      { model: Trade, as: 'sellTrades', required: false },
    ];

    if (status) {
      Object.assign(include[0], { where: { status } });
      Object.assign(include[1], { where: { status } });
    }

    return this.model.findOne({ where, include });
  }

  /**
   * top order book
   * @param {OrderType} orderType
   * @param {CoinType} baseCoinType
   * @param {CoinType} counterCoinType
   * @param {Integer} limit
   * @return {Array<Object>} order book
   */
  topBook(orderType, baseCoinType, counterCoinType, limit) {
    let where = {
      orderType,
      baseCoinType,
      counterCoinType,
      status: OrderStatus.PENDING,
    };
    let group = 'price';
    let order = [['price', orderType == OrderType.limitedBuy ? 'desc' : 'asc']];
    let attributes = ['price', [db.fn('sum', db.col('currentBaseCoinAmount')), 'amount']];

    return this.model.findAll({ where, group, order, attributes, limit });
  }

  /**
   * @param {String} username
   * @param {Array<String>} coinTypes
   * @return {Array}
   */
  getFreezeAmount(username, coinTypes) {
    const sql = `SELECT
        o.baseCoinType AS coinType,
        SUM(o.baseCoinAmount) - SUM(t.baseCoinAmount) AS amount
      FROM Orders AS o 
      LEFT OUTER JOIN Trades AS t 
        ON o.id = t.sellOrderId 
        AND t.status IN ('COMPLETED', 'EXPIRED') 
      WHERE o.username = :username
        AND o.orderType = 'limitedSell' 
        AND o.status IN ('PENDING', 'COMPLETED') 
        AND o.baseCoinType IN (:coinTypes)
      GROUP BY o.baseCoinType`;

    return db.query(sql, { type: 'SELECT', replacements: { username, coinTypes } });
  }
}

module.exports = new OrderDao();
